/**
 ******************************************************************************
 *
 * @file       mapgraphicitem.cpp
 * @author     The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
 * @brief      The main graphicsItem used on the widget, contains the map and map logic
 * @see        The GNU Public License (GPL) Version 3
 * @defgroup   OPMapWidget
 * @{
 *
 *****************************************************************************/
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
#include "uavitem.h"
#include "gpsitem.h"
#include "homeitem.h"
#include "mapgraphicitem.h"
#include <QGraphicsSceneMouseEvent>

namespace mapcontrol {
MapGraphicItem::MapGraphicItem(internals::Core *core, Configuration *configuration) : core(core), config(configuration), MapRenderTransform(1),
    maxZoom(17), minZoom(2), zoomReal(0), zoomDigi(0), isSelected(false), rotation(0)
{
    dragons.load(QString::fromUtf8(":/markers/images/dragons1.jpg"));
    showTileGridLines = false;
    isMouseOverMarker = false;
    maprect = QRectF(0, 0, 1022, 680);
    core->SetCurrentRegion(internals::Rectangle(0, 0, maprect.width(), maprect.height()));
    core->SetMapType(MapType::GoogleHybrid);
    this->SetZoom(2);
    this->setFlag(ItemIsFocusable);
    connect(core, SIGNAL(OnNeedInvalidation()), this, SLOT(Core_OnNeedInvalidation()));
    connect(core, SIGNAL(OnMapDrag()), this, SLOT(childPosRefresh()));
    connect(core, SIGNAL(OnMapZoomChanged()), this, SLOT(childPosRefresh()));
    setCacheMode(QGraphicsItem::ItemCoordinateCache);
}

void MapGraphicItem::start()
{
    core->StartSystem();
}

void MapGraphicItem::resize(const QRectF &rect)
{
    Q_UNUSED(rect);
    {
        this->prepareGeometryChange();
        maprect = boundingBox(scene()->sceneRect(), rotation);
        this->setTransform(QTransform().translate(-(maprect.width() - scene()->width()) / 2, -(maprect.height() - scene()->height()) / 2));
        this->setTransformOriginPoint(maprect.center().x(), maprect.center().y());
        this->setRotation(rotation);
    }

    core->OnMapSizeChanged(maprect.width(), maprect.height());
    core->SetCurrentRegion(internals::Rectangle(0, 0, maprect.width(), maprect.height()));
    if (isVisible()) {
        core->GoToCurrentPosition();
    }
}

QRectF MapGraphicItem::boundingRect() const
{
    const int Margin = 1;

    return maprect.adjusted(-Margin, -Margin, +Margin, +Margin);
}
void MapGraphicItem::Core_OnNeedInvalidation()
{
    this->update();
    emit childRefreshPosition();
}
void MapGraphicItem::childPosRefresh()
{
    emit childRefreshPosition();
}
void MapGraphicItem::setOverlayOpacity(qreal value)
{
    emit childSetOpacity(value);
}
void MapGraphicItem::ConstructLastImage(int const & zoomdiff)
{
    QImage temp;
    QSize size = boundingRect().size().toSize();

    size.setWidth(size.width() * 2 * zoomdiff);
    size.setHeight(size.height() * 2 * zoomdiff);
    temp = QImage(size, QImage::Format_ARGB32_Premultiplied);
    temp.fill(0);
    QPainter imagePainter(&temp);
    imagePainter.translate(-boundingRect().topLeft());
    imagePainter.scale(2 * zoomdiff, 2 * zoomdiff);
    paintImage(&imagePainter);
    imagePainter.end();
    lastimagepoint = Point(core->GetrenderOffset().X() * 2 * zoomdiff, core->GetrenderOffset().Y() * 2 * zoomdiff);
    lastimage = temp;
}
void MapGraphicItem::paintImage(QPainter *painter)
{
    if (MapRenderTransform != 1) {
        QTransform transform;
        transform.translate(-((boundingRect().width() * MapRenderTransform) - (boundingRect().width())) / 2, -((boundingRect().height() * MapRenderTransform) - (boundingRect().height())) / 2);
        transform.scale(MapRenderTransform, MapRenderTransform);
        painter->setWorldTransform(transform);
        {
            DrawMap2D(painter);
        }
        painter->resetTransform();
    } else {
        DrawMap2D(painter);
    }
    // painter->drawRect(maprect);
}
void MapGraphicItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(option);
    Q_UNUSED(widget);

    if (MapRenderTransform != 1) {
        QTransform transform;
        transform.translate(-((boundingRect().width() * MapRenderTransform) - (boundingRect().width())) / 2, -((boundingRect().height() * MapRenderTransform) - (boundingRect().height())) / 2);
        transform.scale(MapRenderTransform, MapRenderTransform);

        painter->setWorldTransform(transform);
        painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
        painter->setRenderHint(QPainter::HighQualityAntialiasing, true);

        {
            DrawMap2D(painter);
        }
        painter->resetTransform();
    } else {
        DrawMap2D(painter);
    }
}
void MapGraphicItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    if (core->IsDragging()) {
        if (MapRenderTransform != 1) {
            qreal dx = (event->pos().x() - core->mouseDown.X()) / (MapRenderTransform);
            qreal dy = (event->pos().y() - core->mouseDown.Y()) / (MapRenderTransform);
            qreal nx = core->mouseDown.X() + dx;
            qreal ny = core->mouseDown.Y() + dy;
            core->mouseCurrent.SetX(nx);
            core->mouseCurrent.SetY(ny);
        } else {
            core->mouseCurrent.SetX(event->pos().x());
            core->mouseCurrent.SetY(event->pos().y());
        }
        {
            core->Drag(core->mouseCurrent);
        }
    } else if (isSelected && !selectionStart.IsEmpty() && (event->modifiers() == Qt::ControlModifier || event->modifiers() == Qt::ShiftModifier)) {
        selectionEnd = FromLocalToLatLng(event->pos().x(), event->pos().y());
        {
            internals::PointLatLng p1 = selectionStart;
            internals::PointLatLng p2 = selectionEnd;

            double x1 = qMin(p1.Lng(), p2.Lng());
            double y1 = qMax(p1.Lat(), p2.Lat());
            double x2 = qMax(p1.Lng(), p2.Lng());
            double y2 = qMin(p1.Lat(), p2.Lat());

            SetSelectedArea(internals::RectLatLng(y1, x1, x2 - x1, y1 - y2));
        }
    }
    QGraphicsItem::mouseMoveEvent(event);
}
void MapGraphicItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    if (!IsMouseOverMarker()) {
        if (event->button() == config->DragButton && CanDragMap() && !((event->modifiers() == Qt::ShiftModifier) || (event->modifiers() == Qt::ControlModifier))) {
            core->mouseDown.SetX(event->pos().x());
            core->mouseDown.SetY(event->pos().y());


            this->setCursor(Qt::SizeAllCursor);

            core->BeginDrag(core->mouseDown);
            this->update();
        } else if (!isSelected && ((event->modifiers() == Qt::ControlModifier) || (event->modifiers() == Qt::ShiftModifier))) {
            isSelected     = true;
            SetSelectedArea(internals::RectLatLng::Empty);
            selectionEnd   = internals::PointLatLng::Empty;
            selectionStart = FromLocalToLatLng(event->pos().x(), event->pos().y());
        }
    }
}
void MapGraphicItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    if (isSelected) {
        isSelected = false;
    }

    if (core->IsDragging()) {
        core->EndDrag();

        this->setCursor(Qt::ArrowCursor);
        if (!BoundsOfMap.IsEmpty() && !BoundsOfMap.Contains(core->CurrentPosition())) {
            if (!core->LastLocationInBounds.IsEmpty()) {
                core->SetCurrentPosition(core->LastLocationInBounds);
            }
        }
    } else {
        if (!selectionEnd.IsEmpty() && !selectionStart.IsEmpty()) {
            if (!selectedArea.IsEmpty() && event->modifiers() == Qt::ShiftModifier) {
                SetZoomToFitRect(SelectedArea());
                selectedArea = internals::RectLatLng::Empty;
            }
        }
    }
}
void MapGraphicItem::keyPressEvent(QKeyEvent *event)
{
    if (event->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier)) {
        this->setCursor(Qt::CrossCursor);
    }
    if (event->key() == Qt::Key_Escape) {
        selectedArea = internals::RectLatLng::Empty;
    }
    QGraphicsItem::keyPressEvent(event);
}
void MapGraphicItem::keyReleaseEvent(QKeyEvent *event)
{
    if ((event->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier)) == 0) {
        this->setCursor(Qt::ArrowCursor);
    }
}
bool MapGraphicItem::SetZoomToFitRect(internals::RectLatLng const & rect)
{
    int maxZoom = core->GetMaxZoomToFitRect(rect);

    if (maxZoom > 0) {
        internals::PointLatLng center = internals::PointLatLng(rect.Lat() - (rect.HeightLat() / 2), rect.Lng() + (rect.WidthLng() / 2));
        core->SetCurrentPosition(center);

        if (maxZoom > MaxZoom()) {
            maxZoom = MaxZoom();
        }

        if ((int)Zoom() != maxZoom) {
            SetZoom(maxZoom);
        }

        return true;
    }
    return false;
}

void MapGraphicItem::wheelEvent(QGraphicsSceneWheelEvent *event)
{
    if (!IsMouseOverMarker() && !IsDragging()) {
        if (core->GetmouseLastZoom().X() != event->pos().x() && core->mouseLastZoom.Y() != event->pos().y()) {
            if (GetMouseWheelZoomType() == internals::MouseWheelZoomType::MousePositionAndCenter) {
                core->SetCurrentPosition(FromLocalToLatLng(event->pos().x(), event->pos().y()));
            } else if (GetMouseWheelZoomType() == internals::MouseWheelZoomType::ViewCenter) {
                core->SetCurrentPosition(FromLocalToLatLng((int)maprect.width() / 2, (int)maprect.height() / 2));
            } else if (GetMouseWheelZoomType() == internals::MouseWheelZoomType::MousePositionWithoutCenter) {
                core->SetCurrentPosition(FromLocalToLatLng(event->pos().x(), event->pos().y()));
            }

            core->mouseLastZoom.SetX((event->pos().x()));
            core->mouseLastZoom.SetY((event->pos().y()));
        }

        // set mouse position to map center
        if (GetMouseWheelZoomType() != internals::MouseWheelZoomType::MousePositionWithoutCenter) {
            {
                // System.Drawing.Point p = PointToScreen(new System.Drawing.Point(Width/2, Height/2));
                // Stuff.SetCursorPos((int) p.X, (int) p.Y);
            }
        }

        core->MouseWheelZooming = true;

        if (event->delta() > 0) {
            SetZoom(ZoomTotal() + 1);
        } else if (event->delta() < 0) {
            SetZoom(ZoomTotal() - 1);
        }

        core->MouseWheelZooming = false;
    }
}
void MapGraphicItem::DrawMap2D(QPainter *painter)
{
    painter->drawImage(this->boundingRect(), dragons.toImage());
    if (!lastimage.isNull()) {
        painter->drawImage(core->GetrenderOffset().X() - lastimagepoint.X(), core->GetrenderOffset().Y() - lastimagepoint.Y(), lastimage);
    }

    for (int i = -core->GetsizeOfMapArea().Width(); i <= core->GetsizeOfMapArea().Width(); i++) {
        for (int j = -core->GetsizeOfMapArea().Height(); j <= core->GetsizeOfMapArea().Height(); j++) {
            core->SettilePoint(core->GetcenterTileXYLocation());
            core->SettilePoint(Point(core->GettilePoint().X() + i, core->GettilePoint().Y() + j));
            {
                internals::Tile *t = core->Matrix.TileAt(core->GettilePoint());
                if (true) {
                    core->tileRect.SetX(core->GettilePoint().X() * core->tileRect.Width());
                    core->tileRect.SetY(core->GettilePoint().Y() * core->tileRect.Height());
                    core->tileRect.Offset(core->GetrenderOffset());
                    if (core->GetCurrentRegion().IntersectsWith(core->tileRect)) {
                        bool found = false;

                        // render tile
                        // lock(t.Overlays)
                        if (t != 0) {
                            foreach(QByteArray img, t->Overlays) {
                                if (img.count() != 0) {
                                    if (!found) {
                                        found = true;
                                    }
                                    {
                                        painter->drawPixmap(core->tileRect.X(), core->tileRect.Y(), core->tileRect.Width(), core->tileRect.Height(), PureImageProxy::FromStream(img));
                                    }
                                }
                            }
                        }

                        if (showTileGridLines) {
                            painter->setPen(config->EmptyTileBorders);
                            painter->drawRect(core->tileRect.X(), core->tileRect.Y(), core->tileRect.Width(), core->tileRect.Height());
                            {
                                painter->setFont(config->MissingDataFont);
                                painter->setPen(Qt::red);
                                painter->drawText(QRectF(core->tileRect.X(), core->tileRect.Y(), core->tileRect.Width(), core->tileRect.Height()), Qt::AlignCenter, (core->GettilePoint() == core->GetcenterTileXYLocation() ? "CENTER: " : "TILE: ") + core->GettilePoint().ToString());
                            }
                        }

                        // add text if tile is missing
                        if (false) {
                            painter->fillRect(QRectF(core->tileRect.X(), core->tileRect.Y(), core->tileRect.Width(), core->tileRect.Height()), config->EmptytileBrush);
                            painter->setFont(config->MissingDataFont);
                            painter->drawText(QRectF(core->tileRect.X(), core->tileRect.Y(), core->tileRect.Width(), core->tileRect.Height()), config->EmptyTileText);


                            painter->setPen(config->EmptyTileBorders);
                            painter->drawRect(core->tileRect.X(), core->tileRect.Y(), core->tileRect.Width(), core->tileRect.Height());

                            // raise error
                        }
                        if (!SelectedArea().IsEmpty()) {
                            core::Point p1 = FromLatLngToLocal(SelectedArea().LocationTopLeft());
                            core::Point p2 = FromLatLngToLocal(SelectedArea().LocationRightBottom());
                            int x1 = p1.X();
                            int y1 = p1.Y();
                            int x2 = p2.X();
                            int y2 = p2.Y();
                            painter->setPen(Qt::black);
                            painter->setBrush(QBrush(QColor(50, 50, 100, 20)));
                            painter->drawRect(x1, y1, x2 - x1, y2 - y1);
                        }
                    }
                }
            }
        }
    }
}


core::Point MapGraphicItem::FromLatLngToLocal(internals::PointLatLng const & point)
{
    core::Point ret = core->FromLatLngToLocal(point);

    if (MapRenderTransform != 1) {
        ret.SetX((int)(ret.X() * MapRenderTransform));
        ret.SetY((int)(ret.Y() * MapRenderTransform));
        ret.SetX(ret.X() - ((boundingRect().width() * MapRenderTransform) - (boundingRect().width())) / 2);
        ret.SetY(ret.Y() - ((boundingRect().height() * MapRenderTransform) - (boundingRect().height())) / 2);
    }
    return ret;
}
internals::PointLatLng MapGraphicItem::FromLocalToLatLng(int x, int y)
{
    if (MapRenderTransform != 1) {
        x = x + ((boundingRect().width() * MapRenderTransform) - (boundingRect().width())) / 2;
        y = y + ((boundingRect().height() * MapRenderTransform) - (boundingRect().height())) / 2;

        x = (int)(x / MapRenderTransform);
        y = (int)(y / MapRenderTransform);
    }
    return core->FromLocalToLatLng(x, y);
}

double MapGraphicItem::Zoom()
{
    return zoomReal;
}
double MapGraphicItem::ZoomDigi()
{
    return zoomDigi;
}
double MapGraphicItem::ZoomTotal()
{
    return zoomDigi + zoomReal;
}

void MapGraphicItem::SetZoom(double const & value)
{
    if (ZoomTotal() != value) {
        if (value > MaxZoom()) {
            zoomReal = MaxZoom();
            zoomDigi = value - MaxZoom();
        } else if (value < MinZoom()) {
            zoomDigi = 0;
            zoomReal = MinZoom();
        } else {
            zoomDigi = 0;
            zoomReal = value;
        }
        double integer;
        double remainder = modf(value, &integer);
        if (zoomDigi != 0 || remainder != 0) {
            float scaleValue = zoomDigi + remainder + 1;
            {
                MapRenderTransform = scaleValue;
            }
            if (integer > MaxZoom()) {
                integer = MaxZoom();
            }
            SetZoomStep((qint32)(integer));
            // core->GoToCurrentPositionOnZoom();
            this->update();
        } else {
            MapRenderTransform = 1;

            SetZoomStep((qint32)(value));
            zoomReal = ZoomStep();
            this->update();
        }
    }
}
int MapGraphicItem::ZoomStep() const
{
    return core->Zoom();
}
void MapGraphicItem::SetZoomStep(int const & value)
{
    if (value - core->Zoom() > 0 && value <= MaxZoom()) {
        ConstructLastImage(value - core->Zoom());
    } else if (value != MaxZoom()) {
        lastimage = QImage();
    }
    if (value > MaxZoom()) {
        core->SetZoom(MaxZoom());
        emit zoomChanged(MaxZoom() + ZoomDigi(), Zoom(), ZoomDigi());
    } else if (value < MinZoom()) {
        core->SetZoom(MinZoom());
        emit zoomChanged(MinZoom() + ZoomDigi(), Zoom(), ZoomDigi());
    } else {
        core->SetZoom(value);
        emit zoomChanged(value + ZoomDigi(), Zoom(), ZoomDigi());;
    }
}

void MapGraphicItem::Offset(int const & x, int const & y)
{
    core->DragOffset(Point(x, y));
}
void MapGraphicItem::mapRotate(qreal angle)
{
    if (rotation != angle) {
        rotation = angle;
        resize(scene()->sceneRect());
    }
}
QRectF MapGraphicItem::boundingBox(const QRectF &rect, const qreal &angle)
{
    QRectF ret(rect);
    float c = cos(angle * 2 * M_PI / 360);
    float s = sin(angle * 2 * M_PI / 360);

    ret.setHeight(rect.height() * fabs(c) + rect.width() * fabs(s));
    ret.setWidth(rect.width() * fabs(c) + rect.height() * fabs(s));
    return ret;
}
QSize MapGraphicItem::sizeHint() const
{
    core::Size size     = core->projection->GetTileMatrixMaxXY(MinZoom());
    core::Size tilesize = core->projection->TileSize();
    QSize rsize((size.Width() + 1) * tilesize.Width(), (size.Height() + 1) * tilesize.Height());

    return rsize;
}
}
